package singsong;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Mixer;

public class RecordedAudioClip implements AudioClip {

	private final int samplesPerSecond;
	
	private final List<Float> samples = new ArrayList<Float>();
	
	private float maxAmp = 0;
	
	private float minAmp = 0;
	
	public RecordedAudioClip(int samplesPerSecond) {
		this.samplesPerSecond = samplesPerSecond;
	}
	
	static public RecordedAudioClip CreateFromFile(String filename) {
		RegularAudioInputStream input = new RegularAudioInputStream(filename);
		RecordedAudioClip audioClip = new RecordedAudioClip((int)input.getFormat().getSampleRate());
		while (!input.isEndOfStream()) {
			audioClip.record(input.nextSample());
		}
		return audioClip;
	}
	
	public void record(float sample) {
		samples.add(sample);
		if (sample > maxAmp) {
			maxAmp = sample;
		} else if (sample < minAmp) {
			minAmp = sample;
		}
	}
	
	public void append(RecordedAudioClip clip) {
		for (Float sample: clip.samples) {
			record(sample);
		}
	}
	
	public void reset() {
		samples.clear();
		maxAmp = 0;
		minAmp = 0;
	}
	
	public byte[] convertFloatToBytes(float sample, int bits) {
		int bytes = bits / 8;
		byte[] data = new byte[bytes];
		switch (bytes) {
		case 1:
			data[0] = (byte)(sample * Byte.MAX_VALUE);
			break;
		case 2:
			short shortSample = (short)(sample * Short.MAX_VALUE);
			data[1] = (byte)(shortSample >> 8);
			data[0] = (byte)((short)(shortSample * Short.MAX_VALUE) - (data[1] << 8));
			break;
		};
		return data;
    }
	
	public Clip convertToClip(Mixer mixer) {
		AudioFormat audioFormat = new AudioFormat(samplesPerSecond, 8, 2, true, false); 
		int byteLength = samples.size() * (audioFormat.getSampleSizeInBits() / 8);
		byte[] data = new byte[byteLength];
		for (int i = 0; i < samples.size(); i++) {
			byte[] sampleBytes = convertFloatToBytes(samples.get(i), audioFormat.getSampleSizeInBits());
			for (int b = 0; b < sampleBytes.length; b++)
				data[i * audioFormat.getSampleSizeInBits() / 8 + b] = sampleBytes[b];
		}
		ByteArrayInputStream in = new ByteArrayInputStream(data); 
		AudioInputStream audioInputStream = new AudioInputStream(in, audioFormat, samples.size());
			
		DataLine.Info info = new DataLine.Info(Clip.class, audioFormat);
		Clip clip = null;
		try {
			clip = (Clip) mixer.getLine(info);
			clip.open(audioInputStream);
		}
		catch (LineUnavailableException e) {
			System.out.println("Line unavailable...turn off your audio players!");
		}
		catch (IOException e) {
			System.out.println("IOException");
		}
		return clip;
	}
	
	public List<Float> getSamples() {
		return samples;
	}
	
	@Override
	public float getDuration() {
		return ((float) samples.size()) / samplesPerSecond;
	}
	
	public int getSamplesPerSecond() {
		return samplesPerSecond;
	}

	@Override
	public float getMaxAmp() {
		return maxAmp;
	}

	@Override
	public float getMinAmp() {
		return minAmp;
	}

	@Override
	public float getSampleAtFrac(float frac) {
		int index = (int) (samples.size() * frac);
		if (index < 0) {
			index = 0;
		} else if (index >= samples.size()) {
			index = samples.size() - 1;
		}
		return samples.get(index);
	}

	@Override
	public float getSampleAtTime(float time) {
		return getSampleAtFrac(time / getDuration());
	}

	@Override
	public float getVolume() {
		return Math.max(maxAmp, -minAmp);
	}
}
